package edu.pitt.cs.cs2410pj2.core;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

import edu.pitt.cs.cs2410pj2.datamodel.DirectoryEntry;
import edu.pitt.cs.cs2410pj2.datamodel.L1CacheEntry;
import edu.pitt.cs.cs2410pj2.datamodel.L2CacheEntry;
import edu.pitt.cs.cs2410pj2.datamodel.Message;
import edu.pitt.cs.cs2410pj2.datamodel.MessageBus;
import edu.pitt.cs.cs2410pj2.datamodel.MessageType;
import edu.pitt.cs.cs2410pj2.datamodel.State;
import edu.pitt.cs.cs2410pj2.util.AddrInfo;
import edu.pitt.cs.cs2410pj2.util.AddrProcessor;
import edu.pitt.cs.cs2410pj2.util.Parameters;

public class L2Controller {
	private L2Cache l2Cache;
	private TileDirectory directory;
	private int tileId;

	int hitCount = 0;
	int missCount = 0;
	
	public String getHitMissRatio() {
		double ratio = hitCount*1.0/missCount;
		return "HIT:"+hitCount+",MISS:"+missCount+",RATIO"+ratio;
	}
	
	public L2Cache getL2Cache() {
		return l2Cache;
	}

	public void setL2Cache(L2Cache l2Cache) {
		this.l2Cache = l2Cache;
	}

	public TileDirectory getDirectory() {
		return directory;
	}

	public void setDirectory(TileDirectory directory) {
		this.directory = directory;
	}

	public int getTileId() {
		return tileId;
	}

	public void setTileId(int tileId) {
		this.tileId = tileId;
	}

	public Parameters getPara() {
		return para;
	}

	public void setPara(Parameters para) {
		this.para = para;
	}

	private Parameters para;

	public L2Controller(Parameters para, int tileId) {
		this.para = para;
		l2Cache = new L2Cache(para, tileId);
		directory = new TileDirectory(para);
	}

	public boolean isBlockCached(String blockAddr) {
		if(directory.isBlockCached(blockAddr)) {
			hitCount++;
		} else {
			missCount++;
		}
		return directory.isBlockCached(blockAddr);
	}

	public int getBlockState(String blockAddr) {
		return directory.getBlockState(blockAddr);
	}

	public boolean isBlockBusy(String blockAddr) {
		return directory.isBlockBusy(blockAddr);
	}

	// load the data from memory to cache
	public void loadData(String blockAddr, int tileId, MessageBus msgBus) {
		Message msg = new Message();
		msg.setBlockAddr(blockAddr);
		msg.setSystem(true); // A system message
		msg.setType(MessageType.L2LOAD);
		msg.setFrom(tileId);
		msg.setTo(tileId);
		msgBus.sendMessage(msg);
	}

	// replace the addBlock method, when a block is replaced, send a request to
	// invalidate all others
	public void receiveBlock(String blockAddr, String data, int timeCycle,
			MessageBus bus) {
		AddrInfo info = AddrProcessor.processAddr(blockAddr, para.p, para.n2,
				para.a2, para.b);
		int setIndex = info.getSetIndex();
		int cacheTag = info.getCacheTag();

		// L2Cache do the LRU
		String replacedBlockAddrKey = l2Cache.addBlock(setIndex, cacheTag,
				timeCycle, data);

		// Modify directionary Info
		if (replacedBlockAddrKey != null) {
			ArrayList<Integer> owners = directory
					.getOwnersFromKey(replacedBlockAddrKey);
			
			//Invalidate all the owners
			for (int i = 0; i < owners.size(); i++) {
				if (owners.get(i) != tileId) {
					Message message = new Message();
					message.setBlockAddr(blockAddr);
					message.setIsData(false);
					message.setType(MessageType.INVALIDATEFROMLRU);
					message.setFrom(tileId);
					message.setTo(owners.get(i));
					bus.sendMessage(message);
				}
			}
			
			directory.removeBlockKey(replacedBlockAddrKey);
		}
		directory.addBlock(blockAddr, State.INVALID);
	}

	// @deprecated should be using receiveBlock
	// Invoked when the data loading message comes
	// In L2, when the data is loaded from memory to cache, change the status to
	// shared by default, status can be changed by others later
	public void addBlock(String blockAddr, String data, int timeCycle) {
		AddrInfo info = AddrProcessor.processAddr(blockAddr, para.p, para.n2,
				para.a2, para.b);
		int setIndex = info.getSetIndex();
		int cacheTag = info.getCacheTag();

		// L2Cache do the LRU
		String replacedBlockAddrKey = l2Cache.addBlock(setIndex, cacheTag,
				timeCycle, data);

		// Modify directionary Info
		if (replacedBlockAddrKey != null) {

			directory.removeBlockKey(replacedBlockAddrKey);
		}
		directory.addBlock(blockAddr, State.INVALID);
	}

	public void updateBlockTime(String blockAddr, int timeCycle) {
		l2Cache.findBlock(blockAddr).setUpdateTime(timeCycle);
	}

	public void modifyBlockData(String blockAddr, String data, int timeCycle) {
		l2Cache.findBlock(blockAddr).setData(data);
		l2Cache.findBlock(blockAddr).setUpdateTime(timeCycle);
	}

	public L2CacheEntry findBlock(String blockAddr) {
		return l2Cache.findBlock(blockAddr);
	}

	// Find a block with specified setIndex and cacheTag
	public L2CacheEntry findBlock(int setIndex, int cacheTag) {
		return l2Cache.findBlock(setIndex, cacheTag);
	}

	public void setBlockBusy(String blockAddr, boolean busy) {
		directory.setBlockBusy(blockAddr, busy);
	}

	public void addOwner(String blockAddr, int tileId) {
		directory.addOwner(blockAddr, tileId);
	}

	public void clearOwners(String blockAddr) {
		directory.clearOwners(blockAddr);
	}

	public void removeOwner(String blockAddr, int tileId) {
		directory.removeOwner(blockAddr, tileId);
	}

	public ArrayList<Integer> getOwners(String blockAddr) {
		return directory.getOwners(blockAddr);
	}

	// Get the owners of the block, put it into a string
	public String getOwnersList(String blockAddr) {
		ArrayList<Integer> owners = getOwners(blockAddr);
		String ownerList = "";
		for (int i = 0; i < owners.size(); i++) {
			ownerList += owners.get(i) + "\t";
		}
		return ownerList;
	}

	public String getOwnersList(ArrayList<Integer> owners) {
		String ownerList = "";
		for (int i = 0; i < owners.size(); i++) {
			ownerList += owners.get(i) + "\t";
		}
		return ownerList;
	}

	// Get the list of owners from the ownerlist message
	public ArrayList<Integer> getOwnersListFromMsg(String ownerList) {
		ownerList = ownerList.trim();
		System.err.println("MESSAGE:" + ownerList);
		String[] tokens = ownerList.split("\t");
		ArrayList<Integer> owners = new ArrayList<Integer>();
		for (int i = 0; i < tokens.length; i++) {
			owners.add(Integer.parseInt(tokens[i]));
		}
		return owners;
	}

	public void changeBlockState(String blockAddr, int state) {
		directory.changeBlockState(blockAddr, state);
	}

	public String toString() {
		String msg = "";
		Hashtable<String, DirectoryEntry> items = directory.getItems();
		Iterator<String> it = items.keySet().iterator();
		while (it.hasNext()) {
			String key = it.next();
			String blockAddr = items.get(key).getBlockAddr();
			msg += "L2 BLOCK:" + blockAddr + ",state:"
					+ State.getStateName(directory.getBlockState(blockAddr))
					+ ",update time:"
					+ l2Cache.findBlock(blockAddr).getUpdateTime();
			if (items.get(key).getOwners().size() >= 1) {
				msg += ",OWNERS:";
				for (int i = 0; i < items.get(key).getOwners().size(); i++) {
					msg += items.get(key).getOwners().get(i) + ",";
				}
			}
			msg += "\n";
		}
		return msg;
	}

}
